package xyz.lwm.lazycat.beans;

import lombok.AllArgsConstructor;
import org.jetbrains.annotations.NotNull;
import xyz.lwm.lazycat.setting.Setting;
import xyz.lwm.lazycat.utility.CollectionUtil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * design registry impl
 *
 * @author lwm
 */
@AllArgsConstructor
class BeanDesignRegistryImpl implements BeanDesignRegistry {

    private final Map<String, BeanDesign<?>> designMap = new ConcurrentHashMap<>();

    private final BeanRefs beanRefs;

    private final BeanProcessor beanProcessor;

    private final BeanRenamer beanRenamer;

    @Override
    public boolean isRegistered(@NotNull String beanName) {
        return designMap.containsKey(beanName);
    }

    @Override
    public void unregister(@NotNull String beanName) throws BeansException {
        if (!isRegistered(beanName)) {
            throw new BeansException("bean name not exists: " + beanName);
        }
        designMap.remove(beanName);
        beanRefs.remove(beanName);
    }

    @Override
    public <T> void register(BeanDesign<T> design) throws BeansException {
        // rename and set bean name
        design.setName(beanRenamer.rename(design));
        // register
        if (isRegistered(design.getName())) {
            BeanDesign<?> registeredDesign = designMap.get(design.getName());
            // check instantiable, if exists instantiable, throw exception
            if (!registeredDesign.getInstantiable().equals(design.getInstantiable())) {
                throw new BeansException("bean name already exists: " + design.getName());
            }
        }
        designMap.put(design.getName(), design);
        beanRefs.put(design.getName(), new BeanSupplier<>(design, beanRefs, beanProcessor));
    }

    /**
     * 过滤不符合条件的design
     */
    void filterByCondition() {
        BeanDesignConditionMatcher beanDesignMap =
                new BeanDesignConditionMatcher(this, beanRefs);
        Set<String> removing = new HashSet<>();
        // 搜集不符合条件的 bean name
        designMap.values().stream()
                .filter(design -> !beanDesignMap.matches(design))
                .map(BeanDesign::getName).forEach(removing::add);
        // 删除不符合条件的 design 和 bean ref
        removing.forEach(this::unregister);
    }

    @Override
    public BeanDesign<?> getDesign(@NotNull String beanName) throws BeansException {
        return designMap.get(beanName);
    }

    // 创建新的 design map
    @Override
    public Map<String, BeanDesign<?>> getDesigns(@NotNull Predicate<BeanDesign<?>> filter) throws BeansException {
        return new ArrayList<>(designMap.values()).stream().filter(filter)
                .sorted(Comparator.comparingInt(BeanDesign::getOrder))
                .collect(Collectors.toMap(BeanDesign::getName,
                        design -> design,
                        (a, b) -> a,
                        LinkedHashMap::new));
    }

    /**
     * 条件匹配器. 判断 design 的条件是否满足
     */
    private static class BeanDesignConditionMatcher {

        private final Map<String, BeanDesign<?>> designMap;

        private final BeanRefs beanRefs;

        private BeanDesignConditionMatcher(BeanDesignRegistry beanDesignRegistry, BeanRefs beanRefs) {
            this.designMap = beanDesignRegistry.getDesigns(design -> true);
            this.beanRefs = beanRefs;
        }

        public boolean matches(BeanDesign<?> design) {
            // 判断条件是否满足
            // 先判断依赖的条件
            Instantiable instantiable = design.getInstantiable();
            if (instantiable instanceof InstantiableWithMethod) {
                // target dependency 一般是 configuration 类
                Dependency dependency = ((InstantiableWithMethod) instantiable).getTargetDependency();
                BeanRef<?> beanRef = beanRefs.get(dependency);
                // configuration 不匹配, 则配置的 bean 均不匹配
                boolean targetMatches = matches(beanRef.getDesign());
                if (!targetMatches) {
                    return false;
                }
            }

            // 判断自身的条件
            if (CollectionUtil.isEmpty(design.getConditions())) {
                return true;
            }
            // 移除 design, 因为 design 存在, 则会干扰使用判断, 比如 @ConditionalOnBean, 该注解必须忽略 design 自身
            BeanDesign<?> removed = designMap.remove(design.getName());
            try {
                return Arrays.stream(design.getConditions()).allMatch(condition ->
                        condition.matches(new Condition.Context(new HashMap<>(designMap), Setting.get()), design));
            } finally {
                // 恢复 design
                designMap.put(design.getName(), removed);
            }
        }

    }

}
